Revertendo string com emojis 😃

Quando queremos separar uma string letra por letra ou até mesmo reverter um texto utilizamos o método split(), porém, existem diversos problemas em se utilizar este método, a própria documentação faz um breve comentário sobre reverter uma String usando split("") não ser a melhor opção.

Problemas do String.prototype.split("")

Ao fazer .split("") a divisão não é feita "letra por letra" por conta da maneira que o Javascript lida com o strings esta divisão pode não ocorrer como o esperado.

Por exemplo, enquanto este emoji 😃 é representado por 2 caracteres, 😶‍🌫️ esse ocupa 6 caracteres. Isso é:

> "a".length
1
> "😃".length
2
> "😶‍🌫️".length
6
> "H̵̙͗ė̴̘l̴̥͒ḷ̶͂o̶̰͝".length
20

E o resultado de tentar utilizar este método para lidar com simbolos mais complexos é esse:

> "Rust & Go".split("")
Array(9) [ "R", "u", "s", "t", " ", "&", " ", "G", "o" ]

> "𝟘𝟙𝟚𝟛".split('')
Array(8) [ "\ud835", "\udfd8", "\ud835", "\udfd9", "\ud835", "\udfda", "\ud835", "\udfdb" ]

> '🦀 Rust & 🐹 Go'.split('')
Array(15) [ "\ud83e", "\udd80", " ", "R", "u", "s", "t", " ", "&", … ]

> "😶‍🌫️".split('')
Array(6) [ "\ud83d", "\ude36", "‍", "\ud83c", "\udf2b", "️" ]

Tentativas falhas

Nenhum desses métodos resolvem o problema de fato e todos retornam os mesmos output. Emojis compostos [ "😶‍🌫️", "🏳️‍🌈", ...] ainda não serão divididos corretamente.

  • Spread syntax (...)

Utilizar o spread operator (...) na String dentro de um array.

> [..."🦀 Rust & 🐹 Go"]
Array(13) [ "🦀", " ", "R", "u", "s", "t", " ", "&", " ", "🐹", … ]

> [..."😶‍🌫️"]
Array(4) [ "😶", "‍", "🌫", "️" ]
  • Array.from

Nesse método o Array.from te permite transformar a string em um array.

Nesse método é utilizado RegExp, não diria ser uma boa escolha pela legibilidade e compressão do código.

  • Reduce method

Nesse método, por conta do for...of, é necessário criar uma variável para armazenar os valores.

Solução

A solução recente que encontrei com um amigo é utilizando o Intl.Segmenter(). Que server para padronizar datas e textos de acordo com o local, permitindo obter grafemas, palavras ou sentenças de forma precisa.

  • Generator
function* splitEmoji(s) { 
  for (const ch of new Intl.Segmenter().segment(s)) 
    yield ch.segment;
}

console.log([...splitEmoji('😶‍🌫️ 🏁')].reverse()) // ['🏁', ' ', '😶‍🌫️']
console.log([...splitEmoji('👨‍👨‍👧‍👦 🇧🇷')].reverse()) // ['🇧🇷', ' ', '👨‍👨‍👧‍👦']
  • Spread Operator and Array.from
const segmenter = new Intl.Segmenter()

console.log([...segmenter.segment('😶‍🌫️ 🏁')].map(({ segment }) => segment).reverse())
// ['🏁', ' ', '😶‍🌫️']

console.log(Array.from(segmenter.segment('👨‍👨‍👧‍👦 🇧🇷')).map(({ segment }) => segment).reverse())
// ['🇧🇷', ' ', '👨‍👨‍👧‍👦']